package com.ysy.serviceImpl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;


import com.ysy.api.LeafFeignApi;
import com.ysy.api.NoteContentFeignApi;
import com.ysy.api.UserFeignApi;
import com.ysy.config.ThreadPoolConfig;
import com.ysy.constant.RedisConstant;
import com.ysy.constant.RocketMqConstant;
import com.ysy.context.SecurityContextHolder;
import com.ysy.dto.*;
import com.ysy.entity.Note;
import com.ysy.entity.NoteLike;
import com.ysy.entity.NoteTopicRel;
import com.ysy.entity.Topic;
import com.ysy.entity.dbdo.NoteDetailDO;
import com.ysy.entity.dto.NoteLikeOrUnlikeDTO;
import com.ysy.entity.dto.NoteOnlyReq;
import com.ysy.entity.dto.NoteReqDTO;
import com.ysy.entity.dto.NoteTopReq;
import com.ysy.entity.vo.*;
import com.ysy.enums.*;
import com.ysy.mapper.NoteLikeMapper;
import com.ysy.mapper.NoteMapper;
import com.ysy.mapper.NoteTopicRelMapper;
import com.ysy.mapper.TopicMapper;
import com.ysy.persimmons.common.constant.HttpStatus;
import com.ysy.persimmons.common.constant.RedisConstants;
import com.ysy.persimmons.common.exception.ServiceException;
import com.ysy.persimmons.common.response.Response;
import com.ysy.persimmons.common.util.AssertUtil;
import com.ysy.persimmons.common.util.DateUtils;
import com.ysy.persimmons.common.util.StringUtils;
import com.ysy.service.NoteService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.jsonwebtoken.lang.Collections;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Date;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * <p>
 * 笔记表 服务实现类
 * </p>
 *
 * @author yangSy
 * @since 2025-04-23
 */

@Service
public class NoteServiceImpl extends ServiceImpl<NoteMapper, Note> implements NoteService {


    private static final Logger log = LoggerFactory.getLogger(NoteServiceImpl.class);

    /**
     * 本地缓存
     */
    private static  final Cache<Long,NoteDetailVO> LOCAL_NOTE_CACHE = Caffeine.newBuilder()
            .initialCapacity(1000)
            .maximumSize(1000)
            .expireAfterWrite(1, TimeUnit.HOURS)
            .build();

    @Resource
    private RedisTemplate<String,String> redisTemplate;
    @Resource
    private RocketMQTemplate rocketMQTemplate;



    @Resource
    private NoteMapper noteMapper;
    @Resource
    private TopicMapper topicMapper;

    @Resource
    private NoteLikeMapper noteLikeMapper;

    @Resource
    private NoteTopicRelMapper noteTopicRelDao;
    @Resource
    private NoteTopicRelMapper noteTopicRelMapper;

    @Autowired
    @Qualifier(ThreadPoolConfig.NOTE_EXECUTOR)
    private ThreadPoolTaskExecutor noteExecutor;


//    Feign
    @Resource
    private LeafFeignApi leafFeignApi;
    @Resource
    private NoteContentFeignApi noteContentFeignApi;
    @Resource
    private UserFeignApi userFeignApi;




    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<?> addNote(NoteReqDTO note) {


        Long userId = SecurityContextHolder.getUserId();
        AssertUtil.isFalse(userId==null || userId==0,ResponseCodeEnum.NOTE_PUBLISH_FAIL);

        // 校验参数
        NoteTypeEnum noteTypeEnum = NoteTypeEnum.getEnum(note.getType());
        AssertUtil.isFalse(Objects.isNull(noteTypeEnum),ResponseCodeEnum.NOTE_TYPE_ERROR);

        switch (noteTypeEnum) {
            case NOTE_IMAGE_TENT:
                List<String> imgUrl = note.getImgUris();
                AssertUtil.isFalse(Collections.isEmpty(imgUrl),ResponseCodeEnum.NOTE_IMAGE_TENT_NULL);
                AssertUtil.isFalse(imgUrl.size() > 6,ResponseCodeEnum.NOTE_IMAGE_MAX_SIZE);
                break;
            case NOTE_VIDEO:
                AssertUtil.isFalse(StringUtils.isBlank(note.getVideoUrl()),ResponseCodeEnum.NOTE_VIDEO_MAX_SIZE);
                break;
            default:
                break;
        }

        // 校验话题是否为空
        List<Long> topics = note.getTopics();
        boolean topicsTag = false;
        if (!Collections.isEmpty(topics)) {
            topicsTag=true;
            // 去重去查询数据库笔较数量,名称就先不比较
            Set<Long> topicSet = new HashSet<Long>(topics);
            List<Topic> topicsDb = topicMapper.selectByIds(topicSet);
            if (Collections.isEmpty(topics) || topicsDb.size() !=topicSet.size()) {
                throw new ServiceException(ResponseCodeEnum.TOPIC_NOT_FOUND);
            }
        }

        String noteContentID = null;
        if (StringUtils.isNotBlank(note.getContent())) {
            note.setContentEmpty(Boolean.FALSE);
            noteContentID = UUID.randomUUID().toString();
        }

        String noteLeftId = leafFeignApi.getSegmentId("note");
        Long noteId = Long.valueOf(noteLeftId);

        // 存储数据 kv
        Note noteSave = new  Note();
        noteSave.setId(noteId);
        noteSave.setType(note.getType());
        noteSave.setCreatorId(userId);
        noteSave.setTop(note.getTop());
        noteSave.setContentEmpty(note.getContentEmpty());
        noteSave.setTitle(note.getTitle());
        noteSave.setStatus(1);// 这里先直接发布，后续需要审核再改
        noteSave.setImgUrl(StringUtils.join(note.getImgUris(), ","));
        noteSave.setVideoUrl(note.getVideoUrl());
        noteSave.setVisible(note.getVisible());
        noteSave.setContentUuid(noteContentID);
       // noteSave.setContent(note.getContent());

        try {
            // 存储mysql
            noteMapper.insert(noteSave);
            if (topicsTag){
                // 插入话题关联表
                List<NoteTopicRel> noteTopicList = new ArrayList<>();
                Date date = new Date(new java.util.Date().getTime());
                note.getTopics().forEach(topic->{
                    noteTopicList.add(new NoteTopicRel(noteId,topic, date,null));
                });
                noteTopicRelMapper.insert(noteTopicList);
            }

            // 不为空需要存储到文本数据库
            NoteContentDTO noteContentDTO = new NoteContentDTO();
            noteContentDTO.setId(noteContentID);
            noteContentDTO.setContent(note.getContent());
            Response<?> response = noteContentFeignApi.addNoteContent(noteContentDTO);
            if (response.getCode() != HttpStatus.SUCCESS) {
                log.warn("存储笔记kv失败");
                throw new ServiceException(ResponseCodeEnum.NOTE_PUBLISH_FAIL);
            }

        } catch (Exception e) {
            log.error("<<笔记存储失败>>:", e);
            if (StringUtils.isNotBlank(noteContentID)) {
                //删除笔记内容
                NoteContentDTO noteContentDTO = new NoteContentDTO();
                noteContentDTO.setId(noteContentID);
                noteContentFeignApi.delNoteContent(noteContentDTO);
                throw new ServiceException(ResponseCodeEnum.NOTE_PUBLISH_FAIL);
            }
        }
        //

        return Response.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<?> updateNote(NoteReqDTO note) {

        NoteTypeEnum noteTypeEnum = NoteTypeEnum.getEnum(note.getType());
        AssertUtil.isFalse(Objects.isNull(noteTypeEnum),ResponseCodeEnum.NOTE_TYPE_ERROR);

        // 后续优化成策略模式
        switch (noteTypeEnum) {
            case NOTE_IMAGE_TENT:
                List<String> imgUrl = note.getImgUris();
                AssertUtil.isFalse(Collections.isEmpty(imgUrl),ResponseCodeEnum.NOTE_IMAGE_TENT_NULL);
                AssertUtil.isFalse(imgUrl.size() > 6,ResponseCodeEnum.NOTE_IMAGE_MAX_SIZE);
                break;
            case NOTE_VIDEO:
                AssertUtil.isFalse(StringUtils.isBlank(note.getVideoUrl()),ResponseCodeEnum.NOTE_VIDEO_MAX_SIZE);
                break;
            default:
                break;
        }
        boolean flag = false;
        Long userId = SecurityContextHolder.getUserId();

        // 本地
        NoteDetailVO ifPresent = LOCAL_NOTE_CACHE.getIfPresent(note.getId());
        if (Objects.nonNull(ifPresent)) {
            log.info("update 当前笔记ID={}查询走本地缓存", note.getId());
            AssertUtil.isFalse(Objects.isNull(ifPresent.getCreatorId()),ResponseCodeEnum.NOTE_NOT_FOUND);
            flag=true;
        }
        // redis
        String noteCacheKey = RedisConstants.buildNoteCacheKey(note.getId());
        if (!flag) {
            // redis
            String noteCacheRedis = redisTemplate.opsForValue().get(noteCacheKey);
            if (StringUtils.isNotBlank(noteCacheRedis)) {
                ifPresent = JSONObject.parseObject(noteCacheRedis,NoteDetailVO.class);
                log.info("uodate 当前笔记ID={}查询走redis缓存", note.getId());
                flag=true;
            }
        }

        if (!flag) {
            Note noteDb = noteMapper.selectById(note.getId());
            ifPresent = new NoteDetailVO();
            ifPresent.setCreatorId(noteDb.getCreatorId());
            ifPresent.setContentEmpty(noteDb.getContentEmpty());
            ifPresent.setContentUuid(noteDb.getContentUuid());
            // 需要在加
        }

        // 当前请求者
        AssertUtil.isTrue(ifPresent.getCreatorId().equals(userId),ResponseCodeEnum.NOTE_CANT_OPERATE);


        String noteContentUUId = StringUtils.isNotBlank(note.getContent()) ?
                ifPresent.getContentEmpty()       ? UUID.randomUUID().toString(): ifPresent.getContentUuid()  :
                ifPresent.getContentUuid() ==null ? null                        :ifPresent.getContentUuid();

        new LambdaUpdateChainWrapper<>(noteMapper)
                .eq(Note::getId, note.getId())
                .set( Note::getContentUuid,noteContentUUId)
                .set( Note::getContentEmpty,note.getContent()==null? Boolean.TRUE:Boolean.FALSE)
                .set(note.getTitle() != null, Note::getTitle, note.getTitle())
               // .set(note.getContent() != null, Note::getContent, note.getContent())
                .set( Note::getType, note.getType())
                .set(note.getVideoUrl() != null, Note::getVideoUrl, note.getVideoUrl())
                .set(note.getImgUris() != null, Note::getImgUrl, StringUtils.join(note.getImgUris(),","))
              //  .set(note.getContent() != null, Note::getContent, note.getContent())
                .set(note.getTitle() != null, Note::getTitle, note.getTitle())
               // .set(note.getContent() != null, Note::getContent, note.getContent())
                .update();

        // 检验话题是否真实 TODO
        noteTopicRelDao.deleteById(note.getId());
        if (!Collections.isEmpty(note.getTopics())){
            // 插入话题关联表
            Date date = new Date(new java.util.Date().getTime());
            List<NoteTopicRel> noteTopicList = new ArrayList<>();
            note.getTopics().forEach(topic->{
                noteTopicList.add(new NoteTopicRel(note.getId(),topic,date,null));
            });
            noteTopicRelMapper.insert(noteTopicList);
        }
        // 数据存在抖动，后手数据对齐。或者加分布式任务，或者重试
        if (StringUtils.isNotBlank(note.getContent())) {
            NoteContentDTO noteContentDTO = new NoteContentDTO();
            noteContentDTO.setId(noteContentUUId);
            noteContentDTO.setContent(note.getContent());
            Response<?> response = noteContentFeignApi.updateNoteContent(noteContentDTO);
            if (response.getCode() != HttpStatus.SUCCESS) {
                throw new ServiceException(ResponseCodeEnum.SYSTEM_ERROR);
            }
        }else if (StringUtils.isNotBlank(noteContentUUId)) {
           //  去删除
            NoteContentDTO noteContentDTO = new NoteContentDTO();
            noteContentDTO.setId(noteContentUUId);
            noteContentDTO.setContent(note.getContent());
            Response<?> response = noteContentFeignApi.delNoteContent(noteContentDTO);
            if (response.getCode() != HttpStatus.SUCCESS) {
                throw new ServiceException(ResponseCodeEnum.SYSTEM_ERROR);
            }
        }
        redisTemplate.delete(noteCacheKey);
//        LOCAL_NOTE_CACHE.invalidate(note.getId());
        rocketMQTemplate.syncSend(RocketMqConstant.TOPIC_DEL_NOTE_LOCAL_CACHE,note.getId());

//        noteExecutor.submit(()->{
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                // 等待过期
//                e.printStackTrace();
//            }
//
//        });

        Message<String> message = MessageBuilder.withPayload(String.valueOf(note.getId()))
                .build();
        rocketMQTemplate.asyncSend(RocketMqConstant.TOPIC_DELAY_DEL_NOTE_REDIS_CACHE,message,new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("延迟删除缓存成功！");
              // 过期兜底
            }
            @Override
            public void onException(Throwable throwable) {
                log.error("延迟删除缓存失败！",throwable);
            }
        },3000,1);//超时是毫秒，延迟是1单位秒


        return Response.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Response<?> delNote(NoteReqDetail note) {

        // 是否是自己的笔记

        // 删除数据库
        new LambdaUpdateChainWrapper<>(noteMapper)
                .eq(Note::getId, note.getId())
                .set(Note::getStatus, NoteStatusEnum.DELETED.getStatus())
                .update();
        // 删除话题, 不保留先啦
        int affectedRows = noteTopicRelMapper.delete(
                Wrappers.<NoteTopicRel>lambdaQuery()
                        .eq(NoteTopicRel::getNoteId, note.getId())
        );
        log.info("删除笔记ID[{}]的话题关联完成，共删除{}条记录", note.getId(), affectedRows);
        // 若影响的行数为 0，则表示该笔记不存在
       AssertUtil.isTrue(affectedRows<1,ResponseCodeEnum.NOTE_NOT_FOUND);
        // 删除图片或者kv。先保留后续恢复啥的或者后面批量删除
        // 删除redis和本地缓存
        String s = RedisConstants.buildNoteCacheKey(note.getId());
        redisTemplate.delete(s);
        rocketMQTemplate.syncSend(RocketMqConstant.TOPIC_DEL_NOTE_LOCAL_CACHE,note.getId());

        Message<String> message = MessageBuilder.withPayload(String.valueOf(note.getId())).build();
        rocketMQTemplate.asyncSend(RocketMqConstant.TOPIC_DELAY_DEL_NOTE_REDIS_CACHE,message,new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                //
                log.info("延迟删除缓存成功！");
                // 过期兜底
            }
            @Override
            public void onException(Throwable throwable) {
                log.error("延迟删除缓存失败！");
                // 过期兜底
            }
        },3000,1);

        return null;
    }

    @Override
    public Response<?> selectNote(NoteReqDTO note) {
        return null;
    }

    /**
     * 查询单个笔记信息
     * @param note
     * @return
     */
    @SneakyThrows
    @Override
    public Response<?> selectDetailNote(NoteReqDetail note) {

        Long userId = SecurityContextHolder.getUserId();

        // 本地
        NoteDetailVO ifPresent = LOCAL_NOTE_CACHE.getIfPresent(note.getId());
        if (Objects.nonNull(ifPresent)) {
            log.info("当前笔记ID={}查询走本地缓存", note.getId());
            AssertUtil.isFalse(Objects.isNull(ifPresent.getCreatorId()),ResponseCodeEnum.NOTE_NOT_FOUND);
            // 当前请求者
            boolean equals = ifPresent.getCreatorId().equals(userId);
            if (ifPresent.getVisible() == 1 ) {
                AssertUtil.isTrue(equals,ResponseCodeEnum.NOTE_PRIVATE);
            }
            return Response.success(ifPresent);
        }

        // redis
        String noteCacheKey = RedisConstants.buildNoteCacheKey(note.getId());
        String noteCacheRedis = redisTemplate.opsForValue().get(noteCacheKey);
        if (StringUtils.isNotBlank(noteCacheRedis)) {
            NoteDetailVO jsonObject = JSONObject.parseObject(noteCacheRedis,NoteDetailVO.class);
            log.info("当前笔记ID={}查询走redis缓存", note.getId());
            // 当前请求者
            boolean equals = jsonObject.getCreatorId().equals(userId);
            if (jsonObject.getVisible() == 1 ) {
                AssertUtil.isTrue(equals,ResponseCodeEnum.NOTE_PRIVATE);
            }
            noteExecutor.submit(()->{
                LOCAL_NOTE_CACHE.put(note.getId(), jsonObject);
            });

            return Response.success(jsonObject);
        }



        // 查询笔记信息
        NoteDetailDO noteDb = noteMapper.selectByNoteId(note.getId());
        boolean aNull = Objects.isNull(noteDb);
        if (aNull) {
            // 存储空对象，防止恶意请求
            LOCAL_NOTE_CACHE.put(note.getId(),new NoteDetailVO());
        }
        AssertUtil.isFalse(aNull,ResponseCodeEnum.NOTE_NOT_FOUND);



        boolean equals = noteDb.getCreatorId().equals(userId);
        if (noteDb.getVisible() == 1 ) {
            AssertUtil.isTrue(equals,ResponseCodeEnum.NOTE_PRIVATE);
        }

        // 用户信息
        CompletableFuture<UserDTO>  futureUser = CompletableFuture.supplyAsync(()->{
            IdRequestDTO idRequestDTO = new IdRequestDTO();
            idRequestDTO.setId(noteDb.getCreatorId());
            Response<?> response = userFeignApi.findById(idRequestDTO);
            AssertUtil.isTrue(response.getCode()== HttpStatus.SUCCESS,ResponseCodeEnum.SYSTEM_ERROR);
            return (UserDTO) response.getData();
        },noteExecutor);

        // 调用 K-V 存储服务获取内容
        CompletableFuture<NoteContentDTO>  futureNoteContent = CompletableFuture.completedFuture(null);
        boolean equals1 = noteDb.getContentEmpty().equals(Boolean.FALSE);
        if (equals1) {
                futureNoteContent = CompletableFuture.supplyAsync(()->{
                NoteContentDTO noteContentDTO = new NoteContentDTO();
                noteContentDTO.setId(noteDb.getContentUuid());
                Response<?> response = noteContentFeignApi.selectNoteContent(noteContentDTO);
                AssertUtil.isTrue(response.getCode()== HttpStatus.SUCCESS,ResponseCodeEnum.SYSTEM_ERROR);
                return (NoteContentDTO) response.getData();
            },noteExecutor);
        }

        CompletableFuture<NoteContentDTO> finalFutureNoteContent = futureNoteContent;
        CompletableFuture<NoteDetailVO> resultFuture = CompletableFuture
                .allOf(futureUser, futureNoteContent)
                .thenApply(v ->{

                    UserDTO userDTO = null;
                    NoteContentDTO noteContent = null;
                    try {
                        userDTO = futureUser.get(3, TimeUnit.SECONDS);
                        noteContent = finalFutureNoteContent.get(3,TimeUnit.SECONDS);
                    } catch (InterruptedException | ExecutionException | TimeoutException e) {
                        log.error("查询笔记详情,获取用户信息和笔记信息异常",e);
                        throw new ServiceException(ResponseCodeEnum.SYSTEM_ERROR);
                    }
                    NoteDetailVO vo = new NoteDetailVO();
                    vo.setId(noteDb.getId());
                    vo.setType(noteDb.getType());
                    vo.setTop(noteDb.getTop());
                    vo.setContentEmpty(noteDb.getContentEmpty());
                    vo.setTitle(noteDb.getTitle());
                    if (equals1){
                        vo.setContent(noteContent.getContent());
                    }
                    vo.setStatus(noteDb.getStatus());

                    vo.setAvatar(userDTO.getAvatar());
                    vo.setUserId(userDTO.getId());
                    vo.setName(userDTO.getName());
                    if (StringUtils.isNotBlank(noteDb.getImgUrl())) {
                        vo.setImgUris(List.of(noteDb.getImgUrl().split(",")));
                    }
                    vo.setVideoUrl(noteDb.getVideoUrl());
                    vo.setVisible(noteDb.getVisible());
                    List<TopicVO> topicVOS = new ArrayList<>();
                    if (!Collections.isEmpty(noteDb.getTopics())) {
                        topicVOS = noteDb.getTopics().stream().map(
                                        topicDO -> TopicVO.builder()
                                                .id(topicDO.getId())
                                                .name(topicDO.getName())
                                                .build()).toList();

                    }
                    vo.setTopics(topicVOS);
                    return vo;
                });
        //点赞数、收藏数、评论数、徽章

        NoteDetailVO vo = resultFuture.get(6,TimeUnit.SECONDS);
        noteExecutor.submit(()->{
            long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60);
            String jsonString = JSON.toJSONString(vo);
            redisTemplate.opsForValue().set(noteCacheRedis,jsonString,expireSeconds,TimeUnit.SECONDS);
        });

        return Response.success(vo);
    }

    @Override
    public Response<?> delNoteCache(String noteId) {
        LOCAL_NOTE_CACHE.invalidate(Long.valueOf(noteId));
        return Response.success();
    }

    @Override
    public Response<?> updateOnlyMe(NoteOnlyReq note) {

        Long userId = SecurityContextHolder.getUserId();

        AssertUtil.isFalse(userId==null,ResponseCodeEnum.SYSTEM_ERROR);
        // 是否有权限修改
        checkCreatorId(note.getId(),userId.toString());

        new LambdaUpdateChainWrapper<>(noteMapper)
                .eq(Note::getId, note.getId())
                .eq(Note::getStatus, NoteStatusEnum.NORMAL.getStatus())
                .set(Note::getVisible, note.getVisible())
                .update();


        deleteNoteCache(note.getId());

        return Response.success();
    }

    @Override
    public Response<?> updateTop(NoteTopReq note) {

        Long userId = SecurityContextHolder.getUserId();
        AssertUtil.isFalse(userId==null,ResponseCodeEnum.SYSTEM_ERROR);
        // 是否有权限修改
        checkCreatorId(note.getId(),userId.toString());

        new LambdaUpdateChainWrapper<>(noteMapper)
                .eq(Note::getId, note.getId())
                .set(Note::getTop, note.getTop());

        deleteNoteCache(note.getId());

        return Response.success();
    }

    private void deleteNoteCache(Long noteId) {
        String s = RedisConstants.buildNoteCacheKey(noteId);
        redisTemplate.delete(s);
        rocketMQTemplate.syncSend(RocketMqConstant.TOPIC_DEL_NOTE_LOCAL_CACHE,noteId);
        Message<String> message = MessageBuilder.withPayload(String.valueOf(noteId)).build();
        rocketMQTemplate.asyncSend(RocketMqConstant.TOPIC_DELAY_DEL_NOTE_REDIS_CACHE,message,new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                //
                log.info("延迟删除缓存成功！");
                // 过期兜底
            }
            @Override
            public void onException(Throwable throwable) {
                log.error("延迟删除缓存失败！");
                // 过期兜底
            }
        },3000,1);
    }

    private void checkCreatorId(Long noteId, String userId) {
        boolean flag = false;
        // 本地
        NoteDetailVO ifPresent = LOCAL_NOTE_CACHE.getIfPresent(noteId);
        if (Objects.nonNull(ifPresent)) {
            log.info("update 当前笔记ID={}查询走本地缓存", noteId);
            AssertUtil.isFalse(Objects.isNull(ifPresent.getCreatorId()),ResponseCodeEnum.NOTE_NOT_FOUND);
            flag=true;
        }
        // redis
        String noteCacheKey = RedisConstants.buildNoteCacheKey(noteId);
        if (!flag) {
            // redis
            String noteCacheRedis = redisTemplate.opsForValue().get(noteCacheKey);
            if (StringUtils.isNotBlank(noteCacheRedis)) {
                ifPresent = JSONObject.parseObject(noteCacheRedis,NoteDetailVO.class);
                log.info("uodate 当前笔记ID={}查询走redis缓存", noteId);
                flag=true;
            }
        }

        if (!flag) {
            Note noteDb = noteMapper.selectById(noteId);
            ifPresent = new NoteDetailVO();
            ifPresent.setCreatorId(noteDb.getCreatorId());
            ifPresent.setContentEmpty(noteDb.getContentEmpty());
            // 需要在加
        }
        // 当前请求者
        AssertUtil.isTrue(ifPresent.getCreatorId().equals(userId),ResponseCodeEnum.NOTE_CANT_OPERATE);
    }


    /**
     * 笔记点赞
     * @param likeReqVo
     * @return
     */
    @Override
    public Response<?> like(NoteLikeReqVo likeReqVo) {
        Long noteId = likeReqVo.getId();
        Long userId = SecurityContextHolder.getUserId();
        // 判断笔记是否存在
        Long creatorId = checkNoteExistAndGetCreatorId(noteId);
        // 是否已经点过过喜欢了
        // 判断缓存b.bitmap是否存在，不存在就去查数据，已经点赞了就需要处理返回
        String roaringBitmapKey = RedisConstant.buildRoaringBitmapNoteLikeKey(userId);
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/roaring_bitmap_check_note_like.lua")));
        redisScript.setResultType(Long.class);
        Long execute = redisTemplate.execute(redisScript, List.of(roaringBitmapKey), noteId);
        NoteLikeOrUnLikeEnum noteLikeOrUnLikeEnum = NoteLikeOrUnLikeEnum.get(execute);

        AssertUtil.isFalse(Objects.isNull(noteLikeOrUnLikeEnum),ResponseCodeEnum.SYSTEM_ERROR);

        // 检查配置位图
        switch (noteLikeOrUnLikeEnum) {
            case NOTE_ALREADY_LIKE -> throw new ServiceException(ResponseCodeEnum.NOTE_ALREADY_LIKE);
            case NO_EXISTS -> {
                // 缓存中没有数据，需要查询是否已经点过赞
                Long l = noteLikeMapper.selectCount(new QueryWrapper<NoteLike>().eq("user_id", userId).eq("note_id", noteId));
                // 保底1天+随机秒数
                long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
                if (Objects.nonNull(l) && l > 0) {
                    // 进行位图缓存的更新
                    noteExecutor.execute(() -> {
                        batchAddNoteLikeBitmapAndExpire(userId,roaringBitmapKey, expireSeconds);
                    });
                    throw new ServiceException(ResponseCodeEnum.NOTE_ALREADY_LIKE);
                }
                // 查询其他缓存信息跟新，并加入新的
                batchAddNoteLikeBitmapAndExpire(userId,roaringBitmapKey, expireSeconds);
                // Lua 脚本路径
                DefaultRedisScript<Long> redisScript2 = new DefaultRedisScript<>();
                redisScript2.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/roaring_bitmap_add_note_like.lua")));
                // 返回值类型
                redisScript2.setResultType(Long.class);
                redisTemplate.execute(redisScript2, java.util.Collections.singletonList(roaringBitmapKey), noteId, expireSeconds);
            }
        }
        // 跟新zSet数据，为什么存储是主页有显示赞过的笔记
        String userNoteLikeZSetKey = RedisConstant.buildUserNoteLikeKey(userId);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/z_set_add_update_note_like.lua")));
        // 返回值类型
        redisScript.setResultType(Long.class);
        LocalDateTime now = LocalDateTime.now();
        long nowTime = DateUtils.localDateTimeTimestamp(now);
        long result = redisTemplate.execute(redisScript, java.util.Collections.singletonList(userNoteLikeZSetKey), noteId, nowTime);
        NoteLikeOrUnLikeEnum addOrUpdate = NoteLikeOrUnLikeEnum.get(result);
        AssertUtil.isFalse(Objects.isNull(addOrUpdate),ResponseCodeEnum.SYSTEM_ERROR);

        if (addOrUpdate.equals(NoteLikeOrUnLikeEnum.NO_EXISTS)){
            // 查询当前用户最新点赞的 100 篇笔记
            List<NoteLike> noteLikeDOS = noteLikeMapper.selectLikedByUserIdAndLimit(userId, 100);

            if (CollUtil.isNotEmpty(noteLikeDOS)) {
                // 保底1天+随机秒数
                long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
                // 构建 Lua 参数
                Object[] luaArgs = buildNoteLikeZSetLuaArgs(noteLikeDOS, expireSeconds);

                DefaultRedisScript<Long> script3 = new DefaultRedisScript<>();
                // Lua 脚本路径
                script3.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/z_set_batch_add_note_like_expire.lua")));
                // 返回值类型
                script3.setResultType(Long.class);
                redisTemplate.execute(script3, java.util.Collections.singletonList(userNoteLikeZSetKey), luaArgs);

                redisTemplate.execute(redisScript, java.util.Collections.singletonList(userNoteLikeZSetKey), noteId, nowTime);
            }
        }
        // 数据库也不存在的话，这里先落库
        // 同步落库
        NoteLikeOrUnlikeDTO build = NoteLikeOrUnlikeDTO.builder()
                .noteId(noteId)
                .userId(userId)
                .createTime(now)
                .noteCreatorId(creatorId)
                .type(YesOrNoEnum.YES.getCode())
                .build();
        MessageBuilder<String> stringMessageBuilder = MessageBuilder.withPayload(JSON.toJSONString(build));
        String dbTopic = RocketMqConstant.NOTE_LIKE_OR_UNLIKE_TOPIC+":"+ RocketMqConstant.NOTE_LIKE;
        rocketMQTemplate.asyncSendOrderly(dbTopic, stringMessageBuilder, String.valueOf(userId), new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("点赞mq发送成功：{}",sendResult);
            }
            @Override
            public void onException(Throwable throwable) {
                log.info("点赞mq发送失败：",throwable);
            }
        });
        return Response.success();
    }


    /**
     * 点赞取消
     * @param unLikeReqVo
     * @return
     */
    @Override
    public Response<?> unLike(NoteUnLikeReqVo unLikeReqVo) {
        Long noteId = unLikeReqVo.getId();
        Long userId = SecurityContextHolder.getUserId();
        // 判断笔记是否存在
        Long creatorId = checkNoteExistAndGetCreatorId(noteId);
        // 是否已经点过喜欢了才能取消
        // 判断缓存b.bitmap是否存在，不存在就去查数据，已经点赞了就需要处理返回
        String roaringBitmapKey = RedisConstant.buildRoaringBitmapNoteLikeKey(userId);
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/roaring_bitmap_check_note_unlike.lua")));
        redisScript.setResultType(Long.class);
        Long execute = redisTemplate.execute(redisScript, List.of(roaringBitmapKey), noteId);
        NoteLikeOrUnLikeEnum noteLikeOrUnLikeEnum = NoteLikeOrUnLikeEnum.get(execute);

        AssertUtil.isFalse(Objects.isNull(noteLikeOrUnLikeEnum),ResponseCodeEnum.SYSTEM_ERROR);

        switch (noteLikeOrUnLikeEnum) {
            case NOTE_UN_LIKE -> throw new ServiceException(ResponseCodeEnum.NOTE_NO_LIKE);
            case NO_EXISTS -> {

                // 缓存中没有数据，需要查询是否已经点过赞查询数据库
                Long count = noteLikeMapper.selectCount(new QueryWrapper<NoteLike>().eq("user_id", userId).eq("note_id", noteId));
                if (Objects.nonNull(count) && count < 1) {
                    // 进行位图缓存的更新，正常一个人不会频繁去点多个取消
                    noteExecutor.execute(() -> {
                        // 保底1天+随机秒数
                        long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
                        batchAddNoteLikeBitmapAndExpire(userId,roaringBitmapKey, expireSeconds);
                    });
                    throw new ServiceException(ResponseCodeEnum.NOTE_NO_LIKE);
                }
            }
        }
        // 已经点赞过了，可以开始取消
        // 更新z set的点赞笔记取消
        String userNoteLikeKey = RedisConstant.buildUserNoteLikeKey(userId);
        // 处理过程中过期了只能靠数据库兜底，Redis 会隐式创建一个新的 key，所有二进制位默认是 0，存在缓存更新的问题
        redisTemplate.opsForZSet().remove(userNoteLikeKey, noteId);

        NoteLikeOrUnlikeDTO build = NoteLikeOrUnlikeDTO.builder()
                .noteId(noteId)
                .userId(userId)
                .createTime(LocalDateTime.now())
                .noteCreatorId(creatorId)
                .type(YesOrNoEnum.NO.getCode())
                .build();
        MessageBuilder<String> stringMessageBuilder = MessageBuilder.withPayload(JSON.toJSONString(build));
        String noteUnLikeTopic = RocketMqConstant.NOTE_LIKE_OR_UNLIKE_TOPIC+":"+ RocketMqConstant.NOTE_UNLIKE;
        rocketMQTemplate.asyncSendOrderly(noteUnLikeTopic, stringMessageBuilder, String.valueOf(userId), new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("取消点赞mq发送成功：{}",sendResult);
                // 进行位图缓存的更新
            }
            @Override
            public void onException(Throwable throwable) {
                log.info("取消点赞mq发送失败：",throwable);
            }
        });
        return Response.success();
    }

    /**
     * 检查笔记是否存在
     * @param noteId
     * @return
     */
    @Override
    public Long checkNoteExistAndGetCreatorId(Long noteId) {

        NoteDetailVO ifPresent = LOCAL_NOTE_CACHE.getIfPresent(noteId);
        if (Objects.isNull(ifPresent)) {
            //查询redis中是存在
            String noteCacheRedis = RedisConstants.buildNoteCacheKey(noteId);
            Boolean exist = redisTemplate.hasKey(noteCacheRedis);
            if (!exist) {
                // 查询数据库是否存在
                NoteDetailDO noteDetailDO = noteMapper.selectByNoteId(noteId);
                if (Objects.isNull(noteDetailDO)) {
                    throw new ServiceException(ResponseCodeEnum.NOTE_NOT_FOUND);
                }
                // 如果存在的话更新redis缓存，避免都打到数据库
                noteExecutor.submit(()->{
                    NoteReqDetail noted = new NoteReqDetail();
                    noted.setId(noteId);
                    selectDetailNote(noted);
                });
                return noteDetailDO.getCreatorId();
            }
            String message = redisTemplate.opsForValue().get(noteCacheRedis);
            NoteDetailVO vo = JSONObject.parseObject(message,NoteDetailVO.class);
            return vo.getCreatorId();
        }
        return ifPresent.getCreatorId();
    }




    /**
     * 位图更新
     * @param userId
     * @param roaringBitmapKey
     * @param expireSeconds
     */
    private void batchAddNoteLikeBitmapAndExpire(Long userId, String roaringBitmapKey, long expireSeconds) {
        // 位图 key  value 1\0
        // 查询数据库该用户已经点赞的数据信息
        List<NoteLike> noteLikes = noteLikeMapper.selectList(new QueryWrapper<NoteLike>().eq("user_id", userId).eq("status", 1));
        if (!Collections.isEmpty(noteLikes)) {
            // 不为空
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/roaring_bitmap_batch_add_note_like.lua")));
            redisScript.setResultType(Long.class);
            List<Object> luaArgs = Lists.newArrayList();
            noteLikes.forEach(noteLike -> {luaArgs.add(noteLike.getNoteId());});
            luaArgs.add(expireSeconds);
            Long execute = redisTemplate.execute(redisScript, List.of(roaringBitmapKey),luaArgs.toArray());
            log.info("异步执行位图跟新结果给：{}",execute);
        }
    }

    /**
     * 构建lua参数
     * @param noteLikeDOS
     * @param expireSeconds
     * @return
     */
    private Object[] buildNoteLikeZSetLuaArgs(List<NoteLike> noteLikeDOS, long expireSeconds) {
        int argsLength = noteLikeDOS.size() * 2 + 1; // 每个笔记点赞关系有 2 个参数（score 和 value），最后再跟一个过期时间
        Object[] luaArgs = new Object[argsLength];

        int i = 0;
        for (NoteLike noteLikeDO : noteLikeDOS) {
            luaArgs[i] = DateUtils.localDateTimeTimestamp(noteLikeDO.getCreateTime()); // 点赞时间作为 score
            luaArgs[i + 1] = noteLikeDO.getNoteId();          // 笔记ID 作为 ZSet value
            i += 2;
        }
        luaArgs[argsLength - 1] = expireSeconds; // 最后一个参数是 ZSet 的过期时间
        return luaArgs;
    }
}


